﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Xml.Linq;
using LightCAD.Core.Element3d;
using LightCAD.MathLib;
using static System.Collections.Specialized.BitVector32;

namespace LightCAD.Core
{
    /// <summary>
    /// 组件实例引用
    /// </summary>
    public class LcComponentInstance : LcElement,IComponentInstance
    {
        public Vector3 Position => Transform3d.Position;
        public Euler Rotation => Transform3d.Euler;
        public Transform3d Transform3d { get;private set; }
       
        public LcComponentDefinition Definition { get;protected set; }

        public List<AssociatePoint> AssociatePoints { get; }=new List<AssociatePoint>();

        public List<AssociateElement> AssociateElements { get; } =new List<AssociateElement>();

        public LcParameterSet Parameters { get; set; }

        private Solid3dCollection solids;
        public Solid3dCollection Solids
        {
            get
            {
                if (solids == null)
                {
                    solids = this.Definition.Solid3dProvider.GetSolid3dsFunc(this.Parameters);
                }
                return solids;
            }
        }
        private Curve2dGroupCollection curves;
        public Curve2dGroupCollection Curves
        {
            get
            {
                if (curves == null)
                {
                    bool useProjection = false;
                    curves = this.Definition.Curve2dProvider.GetCurve2dsFunc(this.Parameters);
                    if (curves == null)
                    {
                        var curveGrps = new Curve2dGroupCollection();
                        var solids = this.Definition.Solid3dProvider.GetSolid3dsFunc(this.Parameters);
                        foreach (var solid in solids)
                        {
                            if (solid.Geometry == null)
                            {
                                solid.CreateMesh();
                            }
                            if (solid.Geometry == null)
                                return null;

                            curveGrps.Add(new Curve2dGroup()
                            {
                                Curve2ds = Projection.GetProjectionResult(solid.Geometry)
                            });
                        }
                        //useProjection = true;
                        curves = curveGrps;
                    }
                    //if (!useProjection)
                    //{
                        foreach (var grp in curves)
                        {
                            foreach (var c2 in grp.Curve2ds)
                            {
                                if (Transform3d.IsMirror)
                                {
                                    if (Transform3d.Scales.X<0)
                                    {
                                        c2.Mirror(new Vector2(), new Vector2(0, 1));
                                    }else if (Transform3d.Scales.Y < 0)
                                    {
                                        c2.Mirror(new Vector2(), new Vector2(1, 0));
                                    }
                                }
                                c2.RotateAround(new Vector2(), Transform3d.Euler.Z);
                                c2.Translate(Transform3d.Position.X, Transform3d.Position.Y);
                            }

                        }
                    //}
                }
                return curves;
            }
        }
        private object _action;
        public object Rt3DAction
        {
            get
            {
                if (_action == null)
                {
                    LcDocument.Element3dActions.TryGetValue(this.Type, out _action);
                }
                if (_action == null)
                {
                    throw new Exception($"Can't find Action. (ElementType={this.Type})");
                }
                return _action;
            }
        }

        public LcComponentInstance(LcComponentDefinition definition)
        {
            this.Definition = definition;
            this.Transform3d = new Transform3d(Definition.Origin);
            this.Parameters=new LcParameterSet(definition.Parameters);
        }

        public override LcElement Clone()
        {
            var clone = new LcComponentInstance(Definition);
            clone.Copy(this);
            return clone;
        }

        public override void Copy(LcElement src)
        {
            var comref = ((LcComponentInstance)src);
            this.Type = comref.Type;
            this.Parameters.Copy(comref.Parameters);
            this.Definition = comref.Definition;
            this.Transform3d.Copy(comref.Transform3d);
            this.AssociatePoints.AddRange(comref.AssociatePoints.Clone());
            this.AssociateElements.AddRange(comref.AssociateElements.Clone());
        }

        public virtual void Invoke(string methodName, InvokeToken token)
        {
        }

        private ListEx<Vector3> snap3dPoints=null;
        public ListEx<Vector3> Snap3dPoints
        {
            get
            {
                if (snap3dPoints == null)
                    snap3dPoints = GetSnap3dPoint();
                return snap3dPoints;
            }
        }
        public LcComponentInstance ResetSnap3dPoints()
        {
            this.snap3dPoints = null;
            return this;
        }
        protected virtual ListEx<Vector3> GetSnap3dPoint()
        {
            //动态捕捉的点默认从边里面取
            var points = new ListEx<Vector3>();
            for (int i = 0; i < this.Solids.Count; i++)
            {
                var posArray = this.Solids[i].Edge.Verteics;
                for (int j = 0; j < posArray.Length; j += 3)
                {
                    points.PushNoRepeat(new Vector3(posArray[j], posArray[j + 1], posArray[j + 2]));
                }
            }
         
            return points;
        }
        public LcComponentInstance UpdateTransformMatrix() 
        {
            this.OnPropertyChangedBefore(nameof(Transform3d), Transform3d, Transform3d);
            this.Transform3d.Update();
            this.ResetCache();
            this.OnPropertyChangedAfter(nameof(Transform3d), Transform3d, Transform3d);
            return this;
        }

        public override void Mirror(Vector2 axisStart, Vector2 axisEnd)
        {
            var dir = axisEnd - axisStart;
            dir.RotateAround(new Vector2(), Utils.HalfPI).Normalize();

            var plane = new Plane().SetFromNormalAndCoplanarPoint(dir.ToVector3(), axisStart.ToVector3());
            var matrix = new Matrix4().MakeMirror(plane);

            this.OnPropertyChangedBefore(nameof(this.Transform3d.Matrix), this.Transform3d.Matrix, null);
            this.Transform3d.Matrix = this.Transform3d.Matrix.Premultiply(matrix);

            //var p = new Vector3(1, 0, 0).ApplyMatrix4(matrix);

            ResetCache();
            //if (this.Definition.Curve2dProvider.GetCurve2dsFunc != null)
            //{
            //    this.curves = null;
            //    var axisDir = new Vector2().SubVectors(axisEnd, axisStart).Normalize();
            //    foreach (var group in this.Curves)
            //    {
            //        foreach (var curve in group.Curve2ds)
            //        {
            //            curve.Mirror(axisStart, axisDir);
            //        }

            //    }
            //}
            this.OnPropertyChangedAfter(nameof(this.Transform3d.Matrix), this.Transform3d.Matrix, null);
        }

        public override void WriteProperties(Utf8JsonWriter writer, JsonSerializerOptions soptions)
        {
            base.WriteProperties(writer, soptions);

            //writer.WriteStringProperty("TypeName", this.Type.ClassType.Name);

            if (this.Definition != null)
            {
                writer.WriteStringProperty("ComponentId", this.Definition.Uuid);
            }
            if (this.Transform3d != null)
            {
                writer.WriteTransform3dProperty(nameof(this.Transform3d), this.Transform3d);
            }
            var paramType = typeof(LcDataType);

            writer.WriteCollectionProperty("ParameterValues", this.Parameters.Values, soptions, (w, val) =>
            {
                writer.WriteStartObject();
                var pi = this.Parameters.Values.ToList().IndexOf(val);
                var type = this.Parameters.Definition[pi].DataType;
                var typeName = Enum.GetName(paramType, type);
                typeName = val.GetType().Name;
                writer.WriteStringProperty("Type", typeName);
                writer.WritePropertyName("Value");
                if (val is List<Curve2d> curves)
                {
                    writer.WriteStartArray();
                    for (int i = 0; i < curves.Count; i++)
                    {
                        var curve = curves[i];
                        writer.WriteStartObject();
                        writer.WriteNumberProperty(nameof(curve.Type), (int)curve.Type);
                        Curve2dUtils.WriteProperties(writer, curves[i], soptions);
                        writer.WriteEndObject();
                    }
                    writer.WriteEndArray();
                }
                else if(val is Vector3 vec)
                {
                    writer.WriteStartArray();
                    writer.WriteNumberValue(vec.X);
                    writer.WriteNumberValue(vec.Y);
                    writer.WriteNumberValue(vec.Z);
                    writer.WriteEndArray();
                }
                else
                {
                    JsonSerializer.Serialize(writer, val, soptions);
                }
                writer.WriteEndObject();
            });
        }
        public override void ReadProperties(ref JsonElement jele)
        {
            base.ReadProperties(ref jele);


            if (jele.TryGetProperty(nameof(this.Transform3d), out JsonElement _))
            {
                this.Transform3d = jele.ReadTransform3dProperty(nameof(this.Transform3d));
            }

            var exist = jele.TryGetProperty("ParameterValues", out JsonElement prop);
            if (exist && !prop.NullOrUndefined())
            {
                var arr = prop.EnumerateArray().ToArray();
                for (var i = 0; i < arr.Length; i++)
                {
                    var item = arr[i];
                    string type = item.ReadStringProperty("Type");
                    object obj = null;
                    if (type == "List<Curve2d>"||type== "List`1")
                    {
                        obj = item.ReadCurve2dListProperty("Value");
                    }
                    else if (type == "Vector3")
                    {
                        obj = item.ReadVector3dProperty("Value");
                    }
                    else
                    {
                        if (type == "Double")
                        {
                            obj = item.ReadObjectProperty<double>("Value");
                        }
                        else if(type == "String")
                        {
                            obj = item.ReadObjectProperty<string>("Value");
                        }
                        else if(type == "Boolean")
                        {
                            obj = item.ReadObjectProperty<bool>("Value");
                        }
                    }

                    this.Parameters.SetValue(i, obj);
                }
            }
        }
        public override void Scale(Vector2 basePoint, double scaleFactor)
        {
            Matrix3 scaleMatrix = Matrix3.GetScale(scaleFactor, basePoint);
            ResetCache();
        }
        public override void Rotate(Vector2 basePoint, double rotateAngle)
        {
            Matrix3 rotateMatrix = Matrix3.RotateInRadian(rotateAngle, basePoint);
            ResetCache();
        }
        public override void Move(Vector2 startPoint, Vector2 endPoint)
        {
            this.Transform3d.Position.X += endPoint.X - startPoint.X;
            this.Transform3d.Position.Y += endPoint.Y - startPoint.Y;
            this.UpdateTransformMatrix();
            ResetCache();
        }
        public override void Translate(double dx, double dy)
        {
            this.OnPropertyChangedBefore(nameof(this.Position), null, null);
            this.Transform3d.Position.X += dx;
            this.Transform3d.Position.Y += dy;
            this.UpdateTransformMatrix();
            ResetCache();
            this.OnPropertyChangedAfter(nameof(this.Position),null,null);
        }
        public override void Translate(Vector2 vector)
        {
            Translate(vector.X, vector.Y);
        }
        public virtual void ResetCache()
        {
            this.curves = null;
            this.solids = null;
            ResetBoundingBox();
        }
        public override Box2 GetBoundingBox()
        {
            var box = new Box2();
            if (this.Curves == null)
            {
                return null;
            }
            box.SetFromPoints( this.Curves.SelectMany(cs => cs.Curve2ds.SelectMany(c=>c.GetPoints())).ToArray());
            return box;
        }
        public override Box2 GetBoundingBox(Matrix3 matrix)
        {
            var box = new Box2();
            if (this.Curves == null)
            {
                return null;
            }
            box.SetFromPoints(this.Curves.SelectMany(cs => cs.Curve2ds.SelectMany(c => c.GetPoints())).ToArray());
            return box;
        }

        public override bool IntersectWithBox(Polygon2d testPoly, List<RefChildElement> intersectChildren = null)
        {
            var thisBox = this.BoundingBox;
            if (!thisBox.IntersectsBox(testPoly.BoundingBox))
            {
                //如果元素盒子，与多边形盒子不相交，那就可能不相交
                return false;
            }
            var points = this.Curves.SelectMany(cs => cs.Curve2ds.SelectMany(c => c.GetPoints())).ToArray();
            for (int i = 0; i < points.Length-1; i++)
            {
                if (GeoUtils.IsPolygonIntersectLine(testPoly.Points, points[i], points[i + 1]))
                    return true;
            }
            return false;
        }

        public override bool IncludedByBox(Polygon2d testPoly, List<RefChildElement> includedChildren = null)
        {
            var thisBox = this.BoundingBox;
            if (!thisBox.IntersectsBox(testPoly.BoundingBox))
            {
                return false;
            }
            var points = this.Curves.SelectMany(cs => cs.Curve2ds.SelectMany(c => c.GetPoints())).ToArray();
            for (int i = 0; i < points.Length - 1; i++)
            {
                if (GeoUtils.IsPolygonContainsLine(testPoly.Points, points[i], points[i + 1]))
                    return true;
            }
            return false;
        }
    }
}
